import VoiceWsClient from '@/utils/voiceWs'
import AudioCapturer from '@/utils/audioCapture'
import PcmPlayer from '@/utils/pcmPlayer'

export type VoiceCallStatus = 'idle' | 'connecting' | 'ready' | 'recognizing' | 'thinking' | 'speaking' | 'ended' | 'error'

export interface VoiceCallConfig {
  baseURL: string
  chatId: string
}

export interface VoiceCallCallbacks {
  onStatus?: (s: VoiceCallStatus) => void
  onError?: (e: unknown) => void
  onMeter?: (rms: number) => void
}

export class VoiceCallController {
  private ws: VoiceWsClient | null = null
  private capturer: AudioCapturer | null = null
  private player: PcmPlayer | null = null
  private status: VoiceCallStatus = 'idle'

  // VAD
  private isSending = false
  private silenceMs = 0
  private speakingMs = 0
  private vadStartedAt = 0
  private ttsTimer: number | null = null

  constructor(private cbs: VoiceCallCallbacks = {}) {}

  private setStatus(s: VoiceCallStatus) {
    this.status = s
    this.cbs.onStatus?.(s)
  }

  async start(cfg: VoiceCallConfig) {
    if (this.status !== 'idle' && this.status !== 'ended') await this.stop()
    this.setStatus('connecting')

    // ws
    this.ws = new VoiceWsClient(cfg.chatId, { baseURL: cfg.baseURL })
    this.ws.on('open', () => {
      this.ws?.sendJSON({ type: 'START' })
      this.setStatus('ready')
    })
    this.ws.on('binary', (buf) => {
      if (!this.player) this.player = new PcmPlayer({ sampleRate: 22050 })
      this.player.feed(buf)
      this.setStatus('speaking')
    })
    this.ws.on('close', () => {
      // 若被动关闭，回到 idle
      if (this.status !== 'ended') this.setStatus('idle')
    })
    this.ws.connect()

    // 音频采集（先初始化，不立刻开始发送）
    this.capturer = new AudioCapturer({ targetSampleRate: 16000, chunkMs: 200 })
    await this.capturer.start((chunk) => this.handleAudioChunk(chunk))
    // 初始不发送，等待 VAD 触发
    this.isSending = false
    this.silenceMs = 0
    this.speakingMs = 0
  }

  // 简易 VAD：用分片 RMS 判断开始/结束
  private handleAudioChunk(chunk: ArrayBuffer) {
    // 计算 RMS
    const view = new Int16Array(chunk)
    let sum = 0
    for (let i = 0; i < view.length; i++) {
      const v = view[i] / 32768
      sum += v * v
    }
    const rms = Math.sqrt(sum / view.length)
    this.cbs.onMeter?.(rms)
    const frameMs = 200

    const openThresh = 0.05  // 提高开启阈值，减少误触发
    const closeThresh = 0.02  // 提高关闭阈值，需要更明显的静音才结束
    const minSpeakMs = 1200   // 增加最小说话时长，避免短促声音触发
    const endSilenceMs = 1500 // 增加结束静音时长，给大模型更多时间说完

    if (rms > openThresh) {
      this.speakingMs += frameMs
      this.silenceMs = 0
      if (!this.isSending && this.speakingMs >= 80) {
        this.beginRecognize()
        this.vadStartedAt = Date.now()
      }
    } else {
      this.silenceMs += frameMs
    }

    if (this.isSending) {
      // 保护：最短说话时长
      const spokenMs = Date.now() - this.vadStartedAt
      if (rms < closeThresh && this.silenceMs >= endSilenceMs && spokenMs >= minSpeakMs) {
        this.endRecognize()
      } else {
        // 传输音频
        this.ws?.sendBinary(chunk)
      }
    }
  }

  private beginRecognize() {
    if (this.isSending) return
    if (this.ttsTimer !== null) { clearTimeout(this.ttsTimer); this.ttsTimer = null }
    this.ws?.sendJSON({ type: 'RECOGNIZE_START' })
    this.isSending = true
    this.setStatus('recognizing')
  }

  private endRecognize() {
    if (!this.isSending) return
    this.isSending = false
    this.ws?.sendJSON({ type: 'RECOGNIZE_STOP' })
    this.setStatus('thinking')
    // 延迟 1 秒再发送 TTS_START（若后端要求显式指令）
    if (this.ttsTimer !== null) { clearTimeout(this.ttsTimer) }
    this.ttsTimer = window.setTimeout(() => {
      this.ws?.sendJSON({ type: 'TTS_START' })
      this.ttsTimer = null
    }, 1000)
    this.silenceMs = 0
    this.speakingMs = 0
  }

  async stop() {
    if (this.ttsTimer !== null) { clearTimeout(this.ttsTimer); this.ttsTimer = null }
    try { this.capturer?.stop() } catch {}
    try { this.player?.stop() } catch {}
    try { this.ws?.sendJSON({ type: 'END' }) } catch {}
    try { this.ws?.close(1000, 'bye') } catch {}
    this.ws = null
    this.capturer = null
    this.player = null
    this.isSending = false
    this.setStatus('ended')
  }
}

export default VoiceCallController


